package com.liuwei.spring.cloud.biz.support;

import cn.hutool.core.map.MapUtil;
import cn.hutool.system.SystemUtil;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;
import com.liuwei.spring.cloud.common.enums.I18nEnum;
import com.liuwei.spring.cloud.common.utils.DateHelper;
import com.liuwei.spring.cloud.common.utils.Jackson;
import com.liuwei.spring.cloud.common.utils.StringHelper;
import com.liuwei.spring.cloud.framework.spring.ApplicationHelper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.boot.logging.LogLevel;

import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author : AlertHelper
 * @version V1.0
 * @date: 2024/1/2 2:02 PM
 * @Description: 默认静默处理, 即不发送告警消息
 * 告警平台建设 https://zhuanlan.zhihu.com/p/657006363
 */
@Slf4j
public class AlertHelper {
    public static volatile ThreadPoolExecutor executor = null;
    public static volatile Map<String,Object> localCache = new HashMap<>();
    static {
        executor = noticeExecutor();
    }
    private static ThreadPoolExecutor noticeExecutor() {
        //获取cpu核心数
        final int cpu = Runtime.getRuntime().availableProcessors();
        //设置核心线程池数量
        final int corePoolSize = cpu + 1;
        //设置线程池中最大线程数量
        final int maximumPoolSize = cpu * 2 + 1;
        //设置线程空闲时间，超时终止线程，线程池线程数量维持在核心线程池大小(corePoolSize)
        final long keepAliveTime = 1L;
        //设置空闲时间的时间单位
        final TimeUnit timeUnit = TimeUnit.SECONDS;
        //设置阻塞队列，用来存储等待执行的任务，如果当前线程数量已超过核心线程设定的数量，则把任务暂时存放在等待队列中
        final int maxQueueNum = 1 << 7;
        //new CustomThreadFactory()：线程工程，用来创建线程
        //new ThreadPoolExecutor.AbortPolicy()：如果线程池已满，新的任务的处理方式
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                timeUnit,
                new LinkedBlockingQueue<Runnable>(maxQueueNum),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        return executor;
    }

    public static RedissonClient redisson(){
        if(ApplicationHelper.getApplicationContext()!=null){
            return ApplicationHelper.getApplicationContext().getBean(RedissonClient.class);
        }
        return null;
    }

    public static Map<String,Object>  localCache(){
        return localCache;
    }

    /*public static void main(String[] args) {
        Map<String,Object> context = new HashMap<>(16);
        context.put("cluster","changzhou");
        context.put("application","wcs");
        context.put("type","Exception");
        context.put("class","cn.hutool.core.exceptions.ValidateException");
        String message = "MOVE_LES_SPS单据在类别:ST2 下存在跳号,当前排序:202312160040";
        context.put("message",message);
        String content = "java.lang.RuntimeException: MOVE_LES_SPS单据在类别:ST2 下存在跳号,当前排序:202312160040\n";
        context.put("content",content);
        String config = """ 
                [{
                	"name": "LES_SPS 跳号告警",
                	"level": "INFO",
                	"matcher": {
                		"type": "AVIATOR",
                		"expression": "string.contains(message,\\"MOVE_LES_SPS单据在类别\\")\\n"
                	},
                	"extractor": {
                		"type": "GROK",
                		"grok": [{
                			"key": "message",
                			"value": "MOVE_LES_SPS单据在类别:%{NOTSPACE:category} 下存在跳号,当前排序:%{NOTSPACE:squence}"
                		}]
                	},
                	"transfer": {
                		"type": "VELOCITY",
                		"vm": "集群信息:   $cluster\\n应用信息:   $application\\nLES大类:    $category\\nLES序号:    $squence\\n告警信息:   $message\\n"
                	},
                	"strategy": {
                		"type": "WINDOW",
                		"interval": 3
                	},
                	"postman": {
                		"type": "FEISHU_WEBHOOK",
                		"webhook": "https://open.feishu.cn/open-apis/bot/v2/hook/ae97d2df-4e6f-4035-99f0-717630da3a67",
                		"at": ""
                	}
                }]
                """;
        List<AlertRule> rules = Jackson.parseArray(config,AlertHelper.AlertRule.class);
        log.info("rules:{}",rules);
        alert(rules,context);
    }*/

    /**
     * TODO: 将 TraceContext 补全到 context 中
     * @param rules
     * @param e
     */
    public static void alert(List<AlertRule> rules,Throwable e){
        Map<String,Object> context = new HashMap<>(16);
        context.put("cluster", SystemUtil.get("spring.application.cluster"));
        context.put("application",SystemUtil.get("spring.application.name"));
        context.put("profile",SystemUtil.get("spring.profiles.active"));
        context.put("type","Exception");
        context.put("class",e.getClass().getSimpleName());
        String message = e.getMessage();
        context.put("message",message);
        String content = StringHelper.getStackTrace(e);
        context.put("content",content);
        alert(rules,context);
    }


    public static void alert(List<AlertRule> rules, Map<String,Object> context){
        CollectionUtils.emptyIfNull(rules).forEach(rule -> {
            // match 判定是否符合消息的匹配条件
            boolean matched = match(rule,context);
            log.info("Alert:{} matched:{}",rule.getName(),matched);
            if(!matched){
                return;
            }
            // extract 提取告警信息
            extract(rule,context);

            // strategy 判定是否符合告警策略
            boolean send = strategy(rule,context);
            log.info("Alert:{} strategy:{}",rule.getName(),send);
            if(!send){
                return;
            }
            // transfer & send 发送消息
            postman(rule,context);
        });
    }

    private static boolean match(AlertRule rule, Map<String, Object> context){
        Matcher matcher = rule.getMatcher();
        if(AlertMatcher.AVIATOR.equals(matcher.getType())){
            String expression = matcher.getExpression();
            if(StringUtils.isEmpty(expression)){
                return false;
            }
            Expression compiledExp = AviatorEvaluator.compile(expression, true);
            Object result =  compiledExp.execute(context);
            log.info("Matched:{}",result);
            return (Boolean)result;
        }
        return false;
    }

    /**
     * 数据预处理
     * @param rule
     * @param context
     * @return
     */

    private static void extract(AlertRule rule, Map<String, Object> context){
        if(ObjectUtils.isEmpty(rule.getExtractor())){
            return;
        }
        List<KeyValue<String,String>> grok = rule.getExtractor().getGrok();
        if(CollectionUtils.isEmpty(grok)){
            return;
        }
        CollectionUtils.emptyIfNull(grok).forEach(x->{
            String key = x.getKey();
            String expression = x.getValue();
            Map<String, Object> extractors = StringHelper.grok(context.get(key).toString(),expression);
            log.info("Extractors:{}",Jackson.toJSONString(extractors));
            if(MapUtil.isNotEmpty(extractors)){
                context.putAll(extractors);
            }
        });
    }

    /**
     * 告警策略
     * @param rule
     * @param context
     * @return
     */
    private static boolean strategy(AlertRule rule, Map<String, Object> context){
        Date now = new Date();
        boolean needSend = false;
        if(AlertStrategy.NONE.equals(rule.getStrategy().getType())){
            return false;
        }
        if(AlertStrategy.DIRECT.equals(rule.getStrategy().getType())){
            return true;
        }
        if(AlertStrategy.WINDOW.equals(rule.getStrategy().getType())){
            String name = rule.getName();
            String flagKey = "application:alert:strategy:window:" + name;
            String counterKey = "application:alert:strategy:counter:" + name;

            int interval = rule.getStrategy().interval;

            // redisson 实现滑动窗口算法
            if(ObjectUtils.isNotEmpty(AlertHelper.redisson())){
                // 通过 RedissonClient 确定发送窗口内是否已经发送
                RedissonClient redisson = AlertHelper.redisson();
                RBucket<Integer> flag = redisson.getBucket(flagKey);
                RAtomicLong counter = redisson.getAtomicLong(counterKey);
                if(ObjectUtils.isEmpty(flag.get())){
                    needSend = true;
                    flag.set(1, Duration.ofMinutes(interval));
                    // 将计数器重新归零
                    counter.getAndSet(1);
                }else{
                    needSend = false;
                    counter.getAndIncrement();
                }
            }
            // 本地环境不支持 redisson
            else{
                Date flag = (Date) localCache.getOrDefault(flagKey,null);
                AtomicInteger counter = (AtomicInteger) localCache.getOrDefault(counterKey,new AtomicInteger());
                if(ObjectUtils.isEmpty(flag) || now.after(DateHelper.addMinutes(flag,5))){
                    needSend = true;
                    localCache.put(flagKey,now);
                    // 将计数器重新归零
                    localCache.put(counterKey,counter);
                    counter.getAndSet(1);
                }else{
                    needSend = false;
                    counter.getAndIncrement();
                }
            }
        }
        return needSend;
    }

    private static void postman(AlertRule rule, Map<String, Object> context){
//        String content = VelocityHelper.render(rule.getTransfer().getVm(),context);
//        if(AlertPlatform.FEISHU_WEBHOOK.equals(rule.getPostman().getType())){
//            String title = rule.getName();
//            String hook = rule.getPostman().getWebhook();
//            ThreadPoolExecutor executor = AlertHelper.executor;
//            CompletableFuture.runAsync(new Runnable() {
//                @Override
//                public void run() {
//                    try {
//                        FeishuHelper.Webhook.sendCardMessage(hook,title, content,null,null);
//                    } catch (Exception exception) {
//                        log.error("系统全局异常信息发送失败",exception);
//                    }
//                }
//            },executor);
//        }
    }

    /**
     * 判断当前 property 是否包含在目标属性中
     * @param searcher
     * @param targetProperties
     * @return
     */
    public static boolean isPropertiesMatched(Map<String, String> searcher, Map<String, String> targetProperties) {
        if (searcher == null || targetProperties == null) {
            return true;
        }
        boolean matched = true;
        for (Map.Entry<String, String> entry : searcher.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            if(ObjectUtils.isEmpty(targetProperties.get(key))){
                matched =  false;
                break;
            }else{
                if(!targetProperties.get(key).equals(value)){
                    matched = false;
                    break;
                }
            }
        }
        return matched;
    }


    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class AlertRule{
        String name;
        LogLevel level = LogLevel.INFO;
        Matcher matcher;
        Extractor extractor;
        Transfer transfer;
        Strategy strategy;
        Postman postman;
    }

    @Data
    public static class Matcher{
        AlertMatcher type = AlertMatcher.AVIATOR;
        String expression;
    }

    @Data
    public static class Extractor {
        AlertExtractor type = AlertExtractor.GROK;
        List<KeyValue<String,String>> grok;
    }

    @Data
    public static class Transfer {
        AlertTransfer type = AlertTransfer.VELOCITY;
        private String vm;
    }

    @Data
    public static class Strategy{
        AlertStrategy type = AlertStrategy.DIRECT;
        int interval = 5;
    }

    @Data
    public static class Postman {
        AlertPlatform type = AlertPlatform.FEISHU_WEBHOOK;
        String webhook ;
        String at;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class KeyValue<K,V>{
        K key;
        V value;
    }

    public enum AlertMatcher implements I18nEnum {
        AVIATOR(0,"aviator")
        ;
        AlertMatcher(int code, String desc) {
            this.code = code;
            this.desc = desc;
        }

        int code;
        String desc;

        @Override
        public int getCode() {
            return this.code;
        }

        @Override
        public String getMessage() {
            return this.desc;
        }
    }

    public enum AlertExtractor implements I18nEnum {
        GROK(0,"grok")
        ;
        AlertExtractor(int code, String desc) {
            this.code = code;
            this.desc = desc;
        }

        int code;
        String desc;

        @Override
        public int getCode() {
            return this.code;
        }

        @Override
        public String getMessage() {
            return this.desc;
        }
    }

    public enum AlertTransfer implements I18nEnum {
        VELOCITY(0,"VELOCITY")
        ;
        AlertTransfer(int code, String desc) {
            this.code = code;
            this.desc = desc;
        }

        int code;
        String desc;

        @Override
        public int getCode() {
            return this.code;
        }

        @Override
        public String getMessage() {
            return this.desc;
        }
    }


    public enum AlertStrategy implements I18nEnum {
        DIRECT(0,"直接发送"),
        WINDOW(1,"窗口发送"),
        NONE(2,"不发送"),
        ;
        AlertStrategy(int code, String desc) {
            this.code = code;
            this.desc = desc;
        }

        int code;
        String desc;

        @Override
        public int getCode() {
            return this.code;
        }

        @Override
        public String getMessage() {
            return this.desc;
        }
    }

    public enum AlertPlatform implements I18nEnum {
        FEISHU_WEBHOOK(0,"飞书webhook"),
        DINGTALK_WEBHOOK(1,"钉钉webhook"),
        ;
        AlertPlatform(int code, String desc) {
            this.code = code;
            this.desc = desc;
        }

        int code;
        String desc;

        @Override
        public int getCode() {
            return this.code;
        }

        @Override
        public String getMessage() {
            return this.desc;
        }
    }
}
